import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

public class Runner {

    public class Controller {
	private Controller() {
	}
	public void hold() {
	    waiting = true;
	    runAction.setEnabled(false);
	    stepAction.setEnabled(false);
	    repeatAction.setEnabled(false);
	}
	public void resume() {
	    waiting = false;
	    runAction.setEnabled(true);
	    stepAction.setEnabled(true);
	    repeatAction.setEnabled(true);
	    if (mode == RUN_MODE) {
		runSteps();
	    } else if (mode == REPEAT_MODE) {
		repeatSteps();
	    }
	}
	public void stop() {
	    setEnabled(false);
	}
    }  // public class Controller

    class InterruptTask
    extends TimerTask {
	public void run() {
	    setPaused(true);
	}
    }  // class InterruptTask

    private static final int RUN_MODE = 0;
    private static final int STEP_MODE = 1;
    private static final int REPEAT_MODE = 2;

    private Runnee runnee;
    private RunUpdater updater;

    private Action runAction;
    private Action stepAction;
    private Action repeatAction;

    private int mode;
    private boolean paused;
    private boolean waiting;
    private int interruptInterval;

    private javax.swing.Timer runTimer;
    private java.util.Timer interrupter;
    private javax.swing.Timer repeatTimer;

    private synchronized boolean isRunning() {
	return mode == RUN_MODE && !paused && !waiting;
    }  // private synchronized boolean isRunning()

    private synchronized void setPaused(boolean p) {
	paused = p;
    }  // private synchronized void setPaused(boolean)

    private void update() {
	if (updater != null) {
	    updater.update();
	}
    }

    private void runSteps() {
	if (mode == RUN_MODE) {
	    runTimer.stop();
	    setPaused(false);
	    TimerTask interruptTask = new InterruptTask();
	    interrupter.schedule(interruptTask, interruptInterval);
	    while (isRunning()) {
		runnee.doStep();
	    }
	    update();
	    if (!waiting) {
		runTimer.restart();
	    }
	}
    }  // private void runSteps()

    private void repeatSteps() {
	if (mode == REPEAT_MODE) {
	    repeatTimer.stop();
	    if (!waiting) {
		runnee.doStep();
		update();
		repeatTimer.restart();
	    }
	}
    }  // private void repeatSteps()

    private Runner() {
    }  // private Runner()

    public Runner(Runnee r, RunUpdater u) {

	runnee = r;
	updater = u;

	runTimer = new javax.swing.Timer(400, new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		runSteps();
	    }
	});
	runTimer.setRepeats(false);
	runTimer.setCoalesce(false);
	interrupter = new java.util.Timer(true);
	runAction = new AbstractAction("Run") {
	    public void actionPerformed(ActionEvent e) {
		if (mode != RUN_MODE) {
		    mode = RUN_MODE;
		    runSteps();
		}
	    }
	};

	stepAction = new AbstractAction("Step") {
	    public void actionPerformed(ActionEvent e) {
		if (mode == STEP_MODE) {
		    runnee.doStep();
		    update();
		} else {
		    mode = STEP_MODE;
		}
	    }
	};

	repeatTimer = new javax.swing.Timer(1000, new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		if (!waiting) {
		    repeatSteps();
		}
	    }
	});
	repeatTimer.setRepeats(false);
	repeatTimer.setCoalesce(false);
	repeatAction = new AbstractAction("Repeat") {
	    public void actionPerformed(ActionEvent e) {
		mode = REPEAT_MODE;
		repeatSteps();
	    }
	};

	waiting = false;
	setEnabled(false);
	interruptInterval = 100;

    }  // public Runner(Runnee, RunUpdater)

    public Runner(RunneeWithInput r, RunUpdater u) {
	this((Runnee)r, u);
	r.setRunnerController(new Controller());
    }  // public Runner(RunneeWithInput)

    public void setEnabled(boolean en) {
	if (!en) {
	    mode = STEP_MODE;
	}
	runAction.setEnabled(en);
	stepAction.setEnabled(en);
	repeatAction.setEnabled(en);
    }  // public void setEnabled(boolean)

    public Action getRunAction() {
	return runAction;
    }  // public Action getRunAction()

    public Action getStepAction() {
	return stepAction;
    }  // public Action getStepAction()

    public Action getRepeatAction() {
	return repeatAction;
    }  // public Action getRepeatAction()

    public int getInterruptInterval() {
	return interruptInterval;
    }  // public int getInterruptInterval()

    public void setInterruptInterval(int ii) {
	interruptInterval = ii;
    }  // public void setInterruptInterval(int)

    public double getLogRepeatFrequency() {
	int repeatTime = repeatTimer.getInitialDelay();
	return (3.0 - Math.log(repeatTime))/Math.log(10.0);
    }  // public double getLogRepeatFrequency()

    public void setLogRepeatFrequency(double lrf) {
	double repeatTime = Math.pow(10.0, 3.0 - lrf);
	repeatTimer.setInitialDelay((int)Math.round(repeatTime));
	repeatSteps();
    }  // public void setLogRepeatFrequency(double)

    private static final Dictionary labels = new Hashtable();
    {
	labels.put(new Integer(-100), new JLabel("0.1"));
	labels.put(new Integer(0), new JLabel("1.0"));
	labels.put(new Integer(100), new JLabel("10.0"));
	labels.put(new Integer(200), new JLabel("100.0"));
    }

    public Component getControlPanel() {
	JPanel buttonPanel = new JPanel(new GridLayout(1, 3));
	buttonPanel.add(new JButton(getRunAction()));
	buttonPanel.add(new JButton(getStepAction()));
	buttonPanel.add(new JButton(getRepeatAction()));
	buttonPanel.setBorder(BorderFactory.createTitledBorder("Mode"));
	final JSlider slider = new JSlider(-100, 200, 0);
	slider.addChangeListener(new ChangeListener() {
	    public void stateChanged(ChangeEvent e) {
		setLogRepeatFrequency(((double)slider.getValue())/100.0);
	    }
	});
	slider.setMajorTickSpacing(100);
	slider.setLabelTable(labels);
	slider.setPaintTicks(true);
	slider.setPaintLabels(true);
	slider.setBorder(BorderFactory.createTitledBorder("Repeat Frequency"));
	JPanel controlPanel = new JPanel(new BorderLayout());
	controlPanel.add(buttonPanel, BorderLayout.NORTH);
	controlPanel.add(slider, BorderLayout.CENTER);
	return controlPanel;
    }

}  // public class Runner

